package cn.bonoon.kernel.support.services.handler;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;

import cn.bonoon.kernel.support.entities.EntityMovable;
import cn.bonoon.kernel.support.entities.EntityTree;

public abstract class AbstractTreeUpAndDownHandler<E extends EntityTree<E>> extends AbstractTreeMovableHandler<E> {
	private final String nearestNodeRoot;
	private final String nearestNodeChild;

	private final String movedownRoot;
	private final String moveupRoot;
	private final String movedownChild;
	private final String moveupChild;

	private final String movedownAllChild;
	private final String movedownAllRoot;
	
	protected AbstractTreeUpAndDownHandler(Class<E> entityClass, String nearestNode, 
			String movedown, String moveup, String movedownAll) {
		super(entityClass);
		String n = entityClass.getName();
		this.nearestNodeRoot = "select x from " + n + " x where x.parent is null and " + nearestNode;
		this.nearestNodeChild = "select x from " + n + " x where x.parent.id=:pid and " + nearestNode;

		movedownRoot = "update " + n + " set ordinal=ordinal+1 where parent is null and " + movedown;
		moveupRoot = "update " + n + " set ordinal=ordinal-1 where parent is null and " + moveup;
		movedownChild = "update " + n + " set ordinal=ordinal+1 where parent.id=? and " + movedown;
		moveupChild = "update " + n + " set ordinal=ordinal-1 where parent.id=? and " + moveup;
		movedownAllChild = "update " + n + " set ordinal=ordinal+1 where parent.id=? and " + movedownAll;
		movedownAllRoot = "update " + n + " set ordinal=ordinal+1 where parent is null and " + movedownAll;
	}
	
//	@Override
//	public Object internalHandler(OperateSource service, OperateEvent event, E node) {
//		EntityManager entityManager = service.getEntityManager();
//		EntityMovable sou = (EntityMovable)node;
//		int ordinal = sou.getOrdinal();
//		E before;
//		if(null == node.getParent()){
//			before = get(entityManager.createQuery(nearestNodeRoot, entityClass)
//					.setParameter(1, ordinal));
//		}else{
//			before = get(entityManager.createQuery(nearestNodeChild, entityClass)
//					.setParameter(1, node.getParent().getId())
//					.setParameter(2, ordinal));
//		}
//		if(null != before){
//			EntityMovable tar = (EntityMovable)before;
//			sou.setOrdinal(tar.getOrdinal());
//			tar.setOrdinal(ordinal);
//			entityManager.merge(sou);
//			entityManager.merge(tar);
//		}
//		return null;
//	}
	
	@Override
	protected Object move(E source, EntityManager entityManager, E oldParent) {
		move(entityManager, source, entityManager.createQuery(nearestNodeChild, entityClass).setParameter("pid", oldParent.getId()));
		return null;
	}
	
	@Override
	protected Object move(E source, EntityManager entityManager) {
		move(entityManager, source, entityManager.createQuery(nearestNodeRoot, entityClass));
		return null;
	}
	
	private void move(EntityManager entityManager, E source, TypedQuery<E> tqSelect){
		EntityMovable sou = (EntityMovable)source;
		int ordinal = sou.getOrdinal();
		E before = get(tqSelect.setParameter("ordinal", ordinal));
		if(null != before){
			EntityMovable tar = (EntityMovable)before;
			sou.setOrdinal(tar.getOrdinal());
			tar.setOrdinal(ordinal);
			entityManager.merge(sou);
			entityManager.merge(tar);
		}
	}

	@Override
	protected void move(E source, E target, EntityManager entityManager) {
		E newParent = target.getParent();
		EntityMovable emTarget = (EntityMovable)target;
		int targetOrdinal = emTarget.getOrdinal();
		if(null == newParent){
			//两者都在根节点下移动
			EntityMovable emSource = (EntityMovable)source;
			int sourceOrdinal = emSource.getOrdinal();
			if(targetOrdinal < sourceOrdinal){
				entityManager.createQuery(movedownRoot)
					.setParameter(1, targetOrdinal)
					.setParameter(2, sourceOrdinal)
					.executeUpdate();
				entityManager.clear();
				emSource.setOrdinal(getOrdinalSourceGreate(targetOrdinal));
				entityManager.merge(emSource);
			}else if(sourceOrdinal < targetOrdinal){
				entityManager.createQuery(moveupRoot)
					.setParameter(1, sourceOrdinal)
					.setParameter(2, targetOrdinal)
					.executeUpdate();
				entityManager.clear();
				emSource.setOrdinal(getOrdinalTargetGreate(targetOrdinal));
				entityManager.merge(emSource);
			}
		}else{
			//计算ordinal
			entityManager.createQuery(movedownAllChild)
				.setParameter(1, newParent.getId())
				.setParameter(2, targetOrdinal)
				.executeUpdate();
			entityManager.clear();
			((EntityMovable)source).setOrdinal(getOrdinalDifferentParent(targetOrdinal));
			source.setParent(newParent);
			source.setFullName(newParent.getFullName() + source.getName());
			source.setXpath(newParent.getXpath() + source.getId() + '/');
			entityManager.merge(source);
			updateChildren(entityManager, source);
			
			newParent.setSize(newParent.getSize() + 1);
			entityManager.merge(newParent);
		}
	}
	
	protected int getOrdinalSourceGreate(int targetOrdinal){return targetOrdinal;}
	protected int getOrdinalTargetGreate(int targetOrdinal){return targetOrdinal;}
	protected int getOrdinalDifferentParent(int targetOrdinal){return targetOrdinal;}
	
	@Override
	protected void move(E source, E target, EntityManager entityManager, E oldSourceParent) {
		E newParent = target.getParent();
		EntityMovable emTarget = (EntityMovable)target;
		int targetOrdinal = emTarget.getOrdinal();
		if(null == newParent){
			entityManager.createQuery(movedownAllRoot)
				.setParameter(1, targetOrdinal)
				.executeUpdate();
			entityManager.clear();
			((EntityMovable)source).setOrdinal(getOrdinalDifferentParent(targetOrdinal));
			source.setParent(null);
			source.setFullName(source.getName());
			source.setXpath(source.getId() + "/");
			entityManager.merge(source);
			updateChildren(entityManager, source);
			
			oldSourceParent.setSize(oldSourceParent.getSize() - 1);
			entityManager.merge(oldSourceParent);
		}else if(newParent.getId().equals(oldSourceParent.getId())){
			EntityMovable emSource = (EntityMovable)source;
			int sourceOrdinal = emSource.getOrdinal();
			if(targetOrdinal < sourceOrdinal){
				entityManager.createQuery(movedownChild)
					.setParameter(1, oldSourceParent.getId())
					.setParameter(2, targetOrdinal)
					.setParameter(3, sourceOrdinal)
					.executeUpdate();
				entityManager.clear();
				emSource.setOrdinal(getOrdinalSourceGreate(targetOrdinal));
				entityManager.merge(emSource);
			}else if(sourceOrdinal < targetOrdinal){
				entityManager.createQuery(moveupChild)
					.setParameter(1, oldSourceParent.getId())
					.setParameter(2, sourceOrdinal)
					.setParameter(3, targetOrdinal)
					.executeUpdate();
				entityManager.clear();
				emSource.setOrdinal(getOrdinalTargetGreate(targetOrdinal));
				entityManager.merge(emSource);
			}
		}else{
			entityManager.createQuery(movedownAllChild)
				.setParameter(1, newParent.getId())
				.setParameter(2, targetOrdinal)
				.executeUpdate();
			entityManager.clear();
			((EntityMovable)source).setOrdinal(getOrdinalDifferentParent(targetOrdinal));
			source.setParent(newParent);
			source.setFullName(newParent.getFullName() + source.getName());
			source.setXpath(newParent.getXpath() + source.getId() + '/');
			entityManager.merge(source);
			updateChildren(entityManager, source);
			
			oldSourceParent.setSize(oldSourceParent.getSize() - 1);
			entityManager.merge(oldSourceParent);
			
			newParent.setSize(newParent.getSize() + 1);
			entityManager.merge(newParent);
		}
	}
}
